﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Security;
using System.Collections.Specialized;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Configuration.Provider;
using System.Security.Cryptography;

namespace curbside.Web.Membership
{
    public class CustomMembershipProvider : MembershipProvider
    {
        private const int SALT_SIZE_IN_BYTES = 16;
        private string applicationName;
        private bool enablePasswordReset;
        private bool enablePasswordRetrieval;
        private int maxInvalidPasswordAttempts;
        private int minRequiredNonAlphanumericCharacters;
        private int minRequiredPasswordLength;
        private int passwordAttemptWindow;
        private MembershipPasswordFormat passwordFormat;
        private string passwordStrengthRegularExpression;
        private bool requiresQuestionAndAnswer;
        private bool requiresUniqueEmail;
        private string hashAlgorithmType;
        private string applicationId = string.Empty;

        #region 公共属性

        /// <summary>
        /// 使用自定义成员资格提供程序的应用程序的名称
        /// </summary>
        public override string ApplicationName
        {
            get { return applicationName; }
            set { this.applicationName = value; }
        }

        /// <summary>
        /// 指示成员资格提供程序是否配置为允许用户重置其密码
        /// </summary>
        public override bool EnablePasswordReset
        {
            get { return enablePasswordReset; }
        }

        /// <summary>
        /// 指示成员资格提供程序是否配置为允许用户检索其密码
        /// </summary>
        public override bool EnablePasswordRetrieval
        {
            get { return enablePasswordRetrieval; }
        }

        /// <summary>
        /// 获取锁定成员资格用户前允许的无效密码或无效密码提示问题答案尝试次数
        /// </summary>
        public override int MaxInvalidPasswordAttempts
        {
            get { return maxInvalidPasswordAttempts; }
        }

        /// <summary>
        /// 获取有效密码中必须包含的最少特殊字符数
        /// </summary>
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return minRequiredNonAlphanumericCharacters; }
        }

        /// <summary>
        /// 获取密码所要求的最小长度
        /// </summary>
        public override int MinRequiredPasswordLength
        {
            get { return minRequiredPasswordLength; }
        }

        /// <summary>
        /// 获取在锁定成员资格用户之前允许的最大无效密码或无效密码提示问题答案尝试次数的分钟数
        /// </summary>
        public override int PasswordAttemptWindow
        {
            get { return passwordAttemptWindow; }
        }

        /// <summary>
        /// 获取一个值，该值指示在成员资格数据存储区中存储密码的格式
        /// </summary>
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return passwordFormat; }
        }

        /// <summary>
        /// 获取用于计算密码的正则表达式
        /// </summary>
        public override string PasswordStrengthRegularExpression
        {
            get { return passwordStrengthRegularExpression; }
        }

        /// <summary>
        /// 获取一个值，该值指示成员资格提供程序是否配置为要求用户在进行密码重置和检索时回答密码提示问题
        /// </summary>
        public override bool RequiresQuestionAndAnswer
        {
            get { return requiresQuestionAndAnswer; }
        }

        /// <summary>
        /// 获取一个值，指示成员资格提供程序是否配置为要求每个用户名具有唯一的电子邮件地址
        /// </summary>
        public override bool RequiresUniqueEmail
        {
            get { return requiresUniqueEmail; }
        }

        /// <summary>
        /// 获取 hash算法类型
        /// </summary>
        public string HashAlgorithmType
        {
            get { return hashAlgorithmType; }
        }

        #endregion

        /// <summary>
        /// 处理更新成员资格用户密码的请求
        /// </summary>
        /// <param name="username">为其更新密码的用户</param>
        /// <param name="oldPassword">指定的用户的当前密码</param>
        /// <param name="newPassword">指定的用户的新密码</param>
        /// <returns></returns>
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            #region 检查参数

            if (!ValidatePassword(newPassword)) return false;
            if (!ValidateUser(username, oldPassword)) return false;
            if (string.IsNullOrEmpty(oldPassword) || string.IsNullOrEmpty(newPassword)) return false;
            if (oldPassword.Trim() == newPassword.Trim()) return true;

            #endregion

            throw new NotImplementedException();
        }

        /// <summary>
        /// 处理更新成员资格用户的密码提示问题和答案的请求
        /// </summary>
        /// <param name="username">要为其更改密码提示问题和答案的用户</param>
        /// <param name="password">指定的用户的密码</param>
        /// <param name="newPasswordQuestion">指定的用户的新密码提示问题</param>
        /// <param name="newPasswordAnswer">指定的用户的新密码提示问题答案</param>
        /// <returns></returns>
        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 将新的成员资格用户添加到数据源
        /// </summary>
        /// <param name="username">新用户的用户名</param>
        /// <param name="password">新用户的密码</param>
        /// <param name="email">新用户的电子邮件地址</param>
        /// <param name="passwordQuestion">新用户的密码提示问题</param>
        /// <param name="passwordAnswer">新用户的密码提示问题答案</param>
        /// <param name="isApproved">是否允许验证新用户</param>
        /// <param name="providerUserKey">成员资格数据源中该用户的唯一标识符</param>
        /// <param name="status">一个 System.Web.Security.MembershipCreateStatus 枚举值，指示是否已成功创建用户</param>
        /// <returns></returns>
        public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status)
        {
            #region 参数检查

            //检查密码
            if (!ValidatePassword(password))
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            //检查用户名
            if (!ValidateParameter(ref username, true, 255))
            {
                status = MembershipCreateStatus.InvalidUserName;
                return null;
            }

            //检查email
            if (!ValidateParameter(ref email, RequiresUniqueEmail, 128))
            {
                status = MembershipCreateStatus.InvalidEmail;
                return null;
            }

            //检查安全问题
            if (!ValidateParameter(ref passwordQuestion, RequiresQuestionAndAnswer, 255))
            {
                status = MembershipCreateStatus.InvalidQuestion;
                return null;
            }

            //检查安全问题答案
            if (!ValidateParameter(ref passwordAnswer, RequiresQuestionAndAnswer, 128))
            {
                status = MembershipCreateStatus.InvalidAnswer;
                return null;
            }

            //检查用户id
            if (userId != null) throw new ArgumentException("userId Parameter must be null");

            #endregion

            status = MembershipCreateStatus.UserRejected;

            throw new NotImplementedException();
        }

        /// <summary>
        /// 从成员资格数据源删除一个用户
        /// </summary>
        /// <param name="username">要删除的用户的名称</param>
        /// <param name="deleteAllRelatedData">如果为 true，则从数据库中删除与该用户相关的数据；如果为 false，则将与该用户相关的数据保留在数据库。</param>
        /// <returns></returns>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 获取一个成员资格用户的集合，其中的电子邮件地址包含要匹配的指定电子邮件地址
        /// </summary>
        /// <param name="emailToMatch">要搜索的电子邮件地址</param>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 是从零开始的。</param>
        /// <param name="pageSize">要返回的结果页的大小</param>
        /// <param name="totalRecords">匹配用户的总数</param>
        /// <returns></returns>
        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 获取一个成员资格用户的集合，其中的用户名包含要匹配的指定用户名
        /// </summary>
        /// <param name="usernameToMatch">要搜索的用户名</param>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 是从零开始的</param>
        /// <param name="pageSize">要返回的结果页的大小</param>
        /// <param name="totalRecords">匹配用户的总数</param>
        /// <returns></returns>
        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 获取数据源中的所有用户的集合，并显示在数据页中
        /// </summary>
        /// <param name="pageIndex">要返回的结果页的索引。pageIndex 是从零开始的。</param>
        /// <param name="pageSize">要返回的结果页的大小。</param>
        /// <param name="totalRecords">匹配用户的总数。</param>
        /// <returns></returns>
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 获取当前访问该应用程序的用户数。
        /// </summary>
        /// <returns></returns>
        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 从数据源获取指定用户名所对应的密码。
        /// </summary>
        /// <param name="username">为其检索密码的用户。</param>
        /// <param name="answer">用户的密码提示问题答案。</param>
        /// <returns></returns>
        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 从数据源获取用户的信息。提供一个更新用户最近一次活动的日期/时间戳的选项。
        /// </summary>
        /// <param name="username">要获取其信息的用户名</param>
        /// <param name="userIsOnline">如果为 true，则更新用户最近一次活动的日期/时间戳；如果为 false，则返回用户信息，但不更新用户最近一次活动的日期/时间戳。</param>
        /// <returns></returns>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 根据成员资格用户的唯一标识符从数据源获取用户信息。提供一个更新用户最近一次活动的日期/时间戳的选项。
        /// </summary>
        /// <param name="providerUserKey">要获取其信息的成员资格用户的唯一标识符</param>
        /// <param name="userIsOnline">如果为 true，则更新用户最近一次活动的日期/时间戳；如果为 false，则返回用户信息，但不更新用户最近一次活动的日期/时间戳。</param>
        /// <returns></returns>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 获取与指定的电子邮件地址关联的用户名
        /// </summary>
        /// <param name="email">要搜索的电子邮件地址</param>
        /// <returns></returns>
        public override string GetUserNameByEmail(string email)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 将用户密码重置为一个自动生成的新密码
        /// </summary>
        /// <param name="username">为其重置密码的用户</param>
        /// <param name="answer">指定的用户的密码提示问题答案</param>
        /// <returns></returns>
        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 清除锁定，以便可以验证该成员资格用户
        /// </summary>
        /// <param name="userName">要清除其锁定状态的成员资格用户</param>
        /// <returns></returns>
        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 更新数据源中有关用户的信息
        /// </summary>
        /// <param name="user">一个 System.Web.Security.MembershipUser 对象，表示要更新的用户及其更新信息</param>
        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 验证数据源中是否存在指定的用户名和密码
        /// </summary>
        /// <param name="username">要验证的用户的名称</param>
        /// <param name="password">指定的用户的密码</param>
        /// <returns></returns>
        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null) throw new ArgumentNullException("config");
            if (String.IsNullOrEmpty(name)) name = "CustomMembershipProvider";
            if (string.IsNullOrEmpty(config["description"])) config["description"] = "Custom Membership Provider";
            base.Initialize(name, config);

            // Get values from config
            enablePasswordReset = GetBooleanValue(config, "enablePasswordReset", true);
            enablePasswordRetrieval = GetBooleanValue(config, "enablePasswordRetrieval", false);
            requiresQuestionAndAnswer = GetBooleanValue(config, "requiresQuestionAndAnswer", true);
            requiresUniqueEmail = GetBooleanValue(config, "requiresUniqueEmail", true);
            maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
            passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
            minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", 7, false, 128);
            minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 128);

            // Get password validation Regular Expression
            passwordStrengthRegularExpression = config["passwordStrengthRegularExpression"];
            if (passwordStrengthRegularExpression != null && passwordStrengthRegularExpression.Trim().Length > 0)
            {
                passwordStrengthRegularExpression = passwordStrengthRegularExpression.Trim();
                try
                {
                    Regex regex = new Regex(passwordStrengthRegularExpression);
                }
                catch (ArgumentException e)
                {
                    throw new ProviderException(e.Message, e);
                }
            }
            else passwordStrengthRegularExpression = string.Empty;

            // Get application name
            applicationName = config["applicationName"];
            if (string.IsNullOrEmpty(applicationName)) applicationName = GetDefaultAppName();
            if (applicationName.Length > 255) throw new ProviderException("Provider application name is too long, max length is 255.");

            // Get hash algorhithm
            hashAlgorithmType = config["hashAlgorithmType"];
            if (String.IsNullOrEmpty(hashAlgorithmType)) hashAlgorithmType = "SHA1";

            // Get password format
            string strTemp = config["passwordFormat"];
            if (strTemp == null) strTemp = "Hashed";
            switch (strTemp)
            {
                case "Clear":
                    passwordFormat = MembershipPasswordFormat.Clear;
                    break;
                case "Encrypted":
                    passwordFormat = MembershipPasswordFormat.Encrypted;
                    break;
                case "Hashed":
                    passwordFormat = MembershipPasswordFormat.Hashed;
                    break;
                default:
                    throw new ProviderException("Bad password format");
            }

            if (passwordFormat == MembershipPasswordFormat.Hashed && enablePasswordRetrieval) throw new ProviderException("Provider cannot retrieve hashed password");

            // Get application ID
            applicationId = GetApplicationID(applicationName, true);
            if (string.IsNullOrEmpty(applicationId)) throw new ProviderException("Provider application ID error.");
        }


        #region 私有方法

        // Get application ID
        private static string GetApplicationID(string applicationName, bool createIfNeeded)
        {
            return Guid.NewGuid().ToString();
        }

        /// <summary>
        /// 得到Int值
        /// </summary>
        /// <param name="config"></param>
        /// <param name="valueName"></param>
        /// <param name="defaultValue"></param>
        /// <param name="zeroAllowed"></param>
        /// <param name="maxValueAllowed"></param>
        /// <returns></returns>
        private static int GetIntValue(NameValueCollection config, string valueName, int defaultValue, bool zeroAllowed, int maxValueAllowed)
        {
            string sValue = config[valueName];
            if (sValue == null) return defaultValue;
            int iValue;
            if (!Int32.TryParse(sValue, out iValue)) throw new Exception("The value must be a positive integer for property '" + valueName + "'");
            if ((zeroAllowed && iValue < 0) || (!zeroAllowed && iValue <= 0)) throw new Exception("The value must be a non-negative integer for property '" + valueName + "'");
            if (maxValueAllowed > 0 && iValue > maxValueAllowed) throw new Exception("The value is too big for '" + valueName + "' must be smaller than " + maxValueAllowed.ToString(CultureInfo.InvariantCulture));

            return iValue;
        }

        /// <summary>
        /// 得到Boolean值
        /// </summary>
        /// <param name="config"></param>
        /// <param name="valueName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private static bool GetBooleanValue(NameValueCollection config, string valueName, bool defaultValue)
        {
            string sValue = config[valueName];
            if (sValue == null) return defaultValue;
            if (sValue == "true") return true;
            if (sValue == "false") return false;
            throw new Exception("The value must be a boolean for property '" + valueName + "'");
        }

        /// <summary>
        /// 得到默认应用程序名称
        /// </summary>
        /// <returns></returns>
        private static string GetDefaultAppName()
        {
            string appName = System.Web.HttpRuntime.AppDomainAppVirtualPath;
            if (string.IsNullOrEmpty(appName)) return "/";

            return appName;
        }

        /// <summary>
        /// 检查参数
        /// </summary>
        /// <param name="param">参数值</param>
        /// <param name="notNullOrEmpty">是否不为null或空字符串</param>
        /// <param name="maxSize">最大长度，0表示不检查</param>
        /// <returns></returns>
        private static bool ValidateParameter(ref string param, bool notNullOrEmpty, int maxSize)
        {
            if (notNullOrEmpty)
            {
                if (string.IsNullOrEmpty(param) || string.Empty == param.Trim()) return false;
            }

            param = param.Trim();
            if (maxSize > 0 && param.Length > maxSize) return false;
            return true;
        }


        /// <summary>
        /// 获得加密密码所使用的Salt
        /// </summary>
        /// <returns></returns>
        private string GenerateSalt()
        {
            byte[] buffer = new byte[SALT_SIZE_IN_BYTES];
            (new RNGCryptoServiceProvider()).GetBytes(buffer);
            return Convert.ToBase64String(buffer);
        }

        /// <summary>
        /// 加密密码字符串
        /// </summary>
        /// <param name="pass">密码</param>
        /// <param name="passwordFormat">密码格式</param>
        /// <param name="salt">密码salt</param>
        /// <returns></returns>
        private string EncodePassword(string pass, int passwordFormat, string salt)
        {
            if (PasswordFormat == 0) return pass;

            byte[] bIn = Encoding.Unicode.GetBytes(pass);
            byte[] bSalt = Convert.FromBase64String(salt);
            byte[] bAll = new byte[bSalt.Length + bIn.Length];
            byte[] bRet = null;

            Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
            Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
            if (passwordFormat == 1)
            {
                // MembershipPasswordFormat.Hashed
                HashAlgorithm s = HashAlgorithm.Create(HashAlgorithmType);

                // If the hash algorithm is null (and came from config), throw a config exception
                if (s == null) throw new ProviderException("Could not create a hash algorithm");

                bRet = s.ComputeHash(bAll);
            }
            else bRet = EncryptPassword(bAll);

            return Convert.ToBase64String(bRet);
        }

        private bool ValidatePassword(string password)
        {

            //检查密码是否为空
            if (!ValidateParameter(ref password, true, 0)) return false;

            //检查密码是否符合正则表达式规则
            if (PasswordStrengthRegularExpression != null)
            {
                if (!Regex.IsMatch(password, PasswordStrengthRegularExpression)) return false;
            }

            //检查密码中特殊字符个数
            if (MinRequiredNonAlphanumericCharacters > 0)
            {
                if (Regex.Matches(password, @"\W").Count < MinRequiredNonAlphanumericCharacters) return false;
            }

            //检查密码是否满足最小长度
            if (password.Length < MinRequiredPasswordLength) return false;

            string salt = GenerateSalt();
            string pass = EncodePassword(password, (int)passwordFormat, salt);
            if (pass.Length > 128) return false;

            return true;
        }

        #endregion
    }
}
